﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Bymed.RegistrationCodeManager.Service.Shared;

namespace Bymed.SpineScan.Service.Shared
{
    public static class GeneralUtility
    {
        public const string LongDateTimeFormat = "yyyy/MM/dd HH:mm:ss";
        public const string ShortDateFormat = "yyyy/MM/dd";
        public const string TimestampFormat = "yyyyMMddHHmmssffff";
        public const string FileNameTimestampFormat = "yyyyMMdd.HHmmssff";

        public enum Gender
        {
            Female = 0,
            Male = 1
        }

        /// <summary>
        /// Get the gender description based on 0 or 1.
        /// </summary>
        /// <param name="gender">The value should 0 or 1.</param>
        /// <returns>0 is Female, 1 is Male.</returns>
        public static string GetGenderDescription(short gender)
        {
            return gender == 0 ? "女" : "男";
        }

        /// <summary>
        /// Check the gender based on 0 or 1.
        /// </summary>
        /// <param name="gender">The value should 0 or 1.</param>
        /// <returns>0 is Female, 1 is Male.</returns>
        public static bool IsMale(short gender)
        {
            return gender == 0 ? false : true;
        }

        /// <summary>
        /// Check the gender based on 0 or 1.
        /// </summary>
        /// <param name="gender">The value should 0 or 1.</param>
        /// <returns>0 is Female, 1 is Male.</returns>
        public static bool IsFemale(short gender)
        {
            return gender == 0 ? true : false;
        }

        /// <summary>
        /// Get current system date time.
        /// </summary>
        /// <returns>Current system date time</returns>
        public static DateTime GetCurrentDateTime()
        {
            return DateTime.Now;
        }

        /// <summary>
        /// Compare two days to check whether they are the same day in the year.
        /// </summary>
        /// <param name="day1">Date of Day1.</param>
        /// <param name="day2">Date of Day2.</param>
        /// <returns>True means these two days are the same. False means different.</returns>
        public static bool CompareDay(DateTime day1, DateTime day2)
        {
            if (day1.Year == day2.Year && day1.DayOfYear == day2.DayOfYear)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// To validate if the input string is a positive integer.
        /// </summary>
        /// <param name="strNum">The int string needs to be validated.</param>
        /// <returns>True means it is a positive integer. False means not.</returns>
        public static bool IsPositiveInteger(string strNum)
        {
            if (string.IsNullOrEmpty(strNum))
            {
                return false;
            }

            string pattern = "^[0-9]+$";

            if (Regex.IsMatch(strNum, pattern, RegexOptions.Compiled))
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Display birthday in formatted string.
        /// </summary>
        /// <param name="birthday">The DateTime of birthday.</param>
        /// <returns>Birthday in formatted string</returns>
        public static string DisplayBirthday(DateTime? birthday)
        {
            return birthday.HasValue ? birthday.Value.ToString(ShortDateFormat) : string.Empty;
        }

        /// <summary>
        /// Get the auto generated timestamp string.
        /// </summary>
        /// <returns>The auto generated timestamp string</returns>
        public static string GetTimestamp()
        {
            return GetCurrentDateTime().ToString(TimestampFormat);
        }

        /// <summary>
        /// Convert to Nullable data type.
        /// </summary>
        /// <typeparam name="T">The input data type.</typeparam>
        /// <param name="value">The input value.</param>
        /// <returns>Nullable data type.</returns>
        public static T? ConvertToNullable<T>(this string value) where T : struct
        {
            if (string.IsNullOrEmpty(value))
            {
                return null;
            }

            return (T)Convert.ChangeType(value, typeof(T));
        }

        /// <summary>
        /// Change the selected item's index.
        /// </summary>
        /// <param name="items">The input item list.</param>
        /// <param name="currentIndex">The message's current index.</param>
        /// <param name="newIndex">The message's new index.</param>
        /// <returns>Current select item.</returns>
        public static T ChangeSelectedItemIndex<T>(IList<T> items, int currentIndex, int newIndex)
        {
            if (currentIndex < items.Count)
            {
                var selectedItem = items[currentIndex];
                items.RemoveAt(currentIndex);
                items.Insert(newIndex, selectedItem);

                return selectedItem;
            }
            else
            {
                return default(T);
            }
        }

        /// <summary>
        /// Get the instance's property value by using the provided property name.
        /// </summary>
        /// <param name="src">The instance that will contains the property.</param>
        /// <param name="propertyName">The property name.</param>
        /// <returns>The instance's property value.</returns>
        public static object GetPropertyValue(object src, string propertyName)
        {
            return src.GetType().GetProperty(propertyName).GetValue(src);
        }

        /// <summary>
        /// Set the instance's property value by using the provided property name and value.
        /// </summary>
        /// <param name="src">The instance that will contains the property.</param>
        /// <param name="propertyName">The property name.</param>
        /// <param name="propertyValue">The property value.</param>
        public static void SetPropertyValue(object src, string propertyName, object propertyValue)
        {
            src.GetType().GetProperty(propertyName).SetValue(src, propertyValue);
        }

        /// <summary>
        /// Check if the property's value is matched with the input value by using property name.
        /// </summary>
        /// <typeparam name="T">The data entity.</typeparam>
        /// <param name="propertyName">The property name.</param>
        /// <param name="inputValue">The input value.</param>
        /// <returns>The Expression tree of Expression<Func<T, bool>>.</returns>
        public static Expression<Func<T, bool>> IsPropertyValueMatchedInputValue<T>(string propertyName, object inputValue)
        {
            var param = Expression.Parameter(typeof(T));

            return Expression.Lambda<Func<T, bool>>(Expression.Equal(Expression.Property(param, propertyName),
                Expression.Constant(inputValue)), param);
        }

        /// <summary>
        /// Create a new instance of provided type and copy all property values.
        /// </summary>
        /// <typeparam name="T">Provided class type.</typeparam>
        /// <param name="source">Source entity contains the data.</param>
        /// <returns>A new instance of provided type with the same property values as the source entity.</returns>
        public static void CloneEntity<T>(T source, T target) where T : class
        {
            var entityType = typeof(T);

            if (target == null)
            {
                target = (T)Activator.CreateInstance(entityType, new object[1] { null });
            }

            foreach (var property in (entityType.GetProperties()))
            {
                if (property.CanWrite)
                {
                    SetPropertyValue(target, property.Name, GetPropertyValue(source, property.Name));
                }
            }
        }

        /// <summary>
        /// Create a new instance of provided type and copy all property values.
        /// </summary>
        /// <typeparam name="T">Provided class type.</typeparam>
        /// <param name="source">Source entity contains the data.</param>
        /// <returns>A new instance of provided type with the same property values as the source entity.</returns>
        public static T CloneEntity<T>(T source) where T : class
        {
            var entityType = typeof(T);
            var entity = (T)Activator.CreateInstance(entityType, new object[1] { null });

            foreach (var property in (entityType.GetProperties()))
            {
                if (property.CanWrite)
                {
                    SetPropertyValue(entity, property.Name, GetPropertyValue(source, property.Name));
                }
            }

            return entity;
        }

        /// <summary>
        /// Get the AvailableAuthorisedUsageCount from app.Config. If the format is invalid, then 0 will be returned.
        /// </summary>
        /// <returns>The AvailableAuthorisedUsageCount.</returns>
        public static int GetAvailableAuthorisedUsageCount()
        {
            var encryptedAvailableAuthorisedUsageCount = (string)ConfigFileUtility.GetValue(ConfigurationConstant.AvailableAuthorisedUsageCount);
            int decryptedAvailableAuthorisedUsageCount = 0;

            // If trying to decrypt the AvailableAuthorisedUsageCount causes error then 0 will be returned.
            try
            {
                var isValidAvailableAuthorisedUsageCount = int.TryParse(RegistrationCodeManagerUtility.DecryptString(encryptedAvailableAuthorisedUsageCount),
                    out decryptedAvailableAuthorisedUsageCount);
            }
            catch { }

            return decryptedAvailableAuthorisedUsageCount;
        }
    }
}
